package com.bytebuddytest.classvisitor;

import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.pool.TypePool;

import java.util.stream.Collectors;

/**
 * @author yutianhong
 * @version 1.0
 * @since 2023/12/21 12:41
 */
public class TypeUtils {
    static final TypePool typePool;

    static {
        ClassLoader extClassLoader = ClassLoader.getSystemClassLoader().getParent();
        ClassFileLocator classLoaderLocator = ClassFileLocator.ForClassLoader.of(extClassLoader);
        typePool = TypePool.Default.of(classLoaderLocator);
    }

    // Bits not (yet) exposed in the public API either because they
    // have different meanings for fields and methods and there is no
    // way to distinguish between the two in this class, or because
    // they are not Java programming language keywords
    static final int BRIDGE = 0x00000040;
    static final int VARARGS = 0x00000080;
    static final int SYNTHETIC = 0x00001000;
    static final int ANNOTATION = 0x00002000;
    static final int ENUM = 0x00004000;
    static final int MANDATED = 0x00008000;

    public static boolean isSynthetic(int mod) {
        return (mod & SYNTHETIC) != 0;
    }

    public static boolean isEnum(int mod, String superClassName) {
        // An enum must both directly extend java.lang.Enum and have
        // the ENUM bit set; classes for specialized enum constants
        // don't do the former.
        return (mod & ENUM) != 0 && "java.lang.Enum".equals(superClassName);
    }

    public static boolean isMandated(int mod) {
        return (mod & MANDATED) != 0;
    }

    public static boolean loadableByExtClassLoader(String className) {
        if (className == null) {
            return false;
        }
        TypePool.Resolution resolution = typePool.describe(className);
        return resolution.isResolved();
    }

    public static boolean implementsInterface(TypeDescription.Generic type, String interfaceName) {
        if (type == null || interfaceName == null) {
            return false;
        }
        return implementsInterface0(type, interfaceName);
    }

    private static boolean implementsInterface0(TypeDescription.Generic type, String interfaceName) {
        if (interfaceName.equals(getTypeNameWithoutGeneric(type))) {
            return true;
        }
        TypeList.Generic interfaces = type.getInterfaces();
        for (TypeDescription.Generic anInterface : interfaces) {
            if (implementsInterface0(anInterface, interfaceName)) {
                return true;
            }
        }
        TypeDescription.Generic superClass = type.getSuperClass();
        return superClass != null && implementsInterface0(superClass, interfaceName);
    }

    public static boolean extendsSuperClass(TypeDescription.Generic type, String superClassName) {
        if (type == null || superClassName == null) {
            return false;
        }
        return extendsSuperClass0(type, superClassName);
    }

    private static boolean extendsSuperClass0(TypeDescription.Generic type, String superClassName) {
        if (superClassName.equals(getTypeNameWithoutGeneric(type))) {
            return true;
        }
        TypeDescription.Generic superClass = type.getSuperClass();
        return superClass != null && extendsSuperClass0(superClass, superClassName);
    }

    public static String getTypeNameWithoutGeneric(TypeDescription.Generic typeDef) {
        if (typeDef == null) {
            return "";
        }
        String typeName = typeDef.getTypeName();
        if (typeDef.getSort().isParameterized()) {
            int index = typeName.indexOf("<");
            if (index != -1) {
                typeName = typeName.substring(0, index);
            }
        }
        return typeName;
    }

    public static String getSimpleNameWithoutGeneric(TypeDescription.Generic type) {
        if (type.isArray()) {
            return getSimpleNameWithoutGeneric(type.getComponentType()) + "[]";
        }
        String name = TypeUtils.getTypeNameWithoutGeneric(type);
        int index = name.lastIndexOf(".");
        if (index >= 0) {
            name = name.substring(index + 1);
        }
        return name;
    }

    public static String getGenericTypeString(TypeDescription.Generic typeDef) {
        if (typeDef.getSort().isParameterized()) {
            TypeList.Generic typeArguments = typeDef.getTypeArguments();
            return typeArguments.stream()
                    .map(TypeUtils::getSimpleNameWithGeneric)
                    .collect(Collectors.joining(",", "<", ">"));
        }
        return "";
    }

    public static String getSimpleNameWithGeneric(TypeDescription.Generic type) {
        String simpleName = TypeUtils.getSimpleNameWithoutGeneric(type);
        if (type.getSort().isParameterized()) {
            simpleName += getGenericTypeString(type);
        } else if (type.getSort().isWildcard()) {
            if (!type.getUpperBounds().isEmpty()) {
                TypeDescription.Generic upper = type.getUpperBounds().get(0);
                if (upper.getTypeName().equals("java.lang.Object")) {
                    simpleName = "?";
                } else {
                    String upperStr = getSimpleNameWithGeneric(upper);
                    simpleName = "? extends " + upperStr;
                }
            } else if (!type.getLowerBounds().isEmpty()) {
                TypeDescription.Generic lower = type.getLowerBounds().get(0);
                String lowerStr = getSimpleNameWithGeneric(lower);
                simpleName += "? super " + lowerStr;
            } else {
                simpleName = "?";
            }
        }
        return simpleName;
    }

    public static TypePool getTypePool() {
        return typePool;
    }

    private TypeUtils() {
    }

    public static void main(String[] args) {
        // test
        assert !loadableByExtClassLoader("com.bytebuddytest.classvisitor.SuperClass");
        assert !loadableByExtClassLoader("com.bytebuddytest.classvisitor.TestClass");
        assert loadableByExtClassLoader("java.util.ArrayList");
        assert loadableByExtClassLoader("java.util.HashMap");

        ClassLoader extClassLoader = ClassLoader.getSystemClassLoader();
        ClassFileLocator classLoaderLocator = ClassFileLocator.ForClassLoader.of(extClassLoader);
        TypePool typePool = TypePool.Default.of(classLoaderLocator);

        TypeDescription.Generic hashSet = typePool.describe("java.util.HashSet").resolve().asGenericType();
        TypeDescription.Generic hashMap = typePool.describe("java.util.HashMap").resolve().asGenericType();
        TypeDescription.Generic arrayList = typePool.describe("java.util.ArrayList").resolve().asGenericType();
        TypeDescription.Generic collections = typePool.describe("java.util.Collections").resolve().asGenericType();
        TypeDescription.Generic testClass = typePool.describe("com.bytebuddytest.classvisitor.TestClass").resolve().asGenericType();
        TypeDescription.Generic iterable = typePool.describe("java.lang.Iterable").resolve().asGenericType();
        TypeDescription.Generic map = typePool.describe("java.util.Map").resolve().asGenericType();
        TypeDescription.Generic list = typePool.describe("java.util.List").resolve().asGenericType();
        TypeDescription.Generic object = typePool.describe("java.lang.Object").resolve().asGenericType();

        assert implementsInterface(hashSet, "java.lang.Iterable");
        assert !implementsInterface(hashSet, "java.util.Map");
        assert implementsInterface(hashMap, "java.util.Map");
        assert !implementsInterface(hashMap, "java.lang.Iterable");
        assert implementsInterface(arrayList, "java.lang.Iterable");
        assert !implementsInterface(collections, "java.lang.Iterable");
        assert !implementsInterface(testClass, "java.lang.Iterable");
        assert !implementsInterface(testClass, "xxx");
        assert !implementsInterface(testClass, "");
        assert !implementsInterface(iterable, "java.util.Map");
        assert implementsInterface(iterable, "java.lang.Iterable");
        assert !implementsInterface(map, "java.lang.Iterable");
        assert implementsInterface(map, "java.util.Map");
        assert implementsInterface(list, "java.lang.Iterable");
        assert !implementsInterface(object, "java.lang.Iterable");
        assert !implementsInterface(object, "java.adadada");
    }

}
